package com.shure.encrypt.plugin;

import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.google.common.base.CaseFormat;
import com.shure.encrypt.annotation.EncryptDecryptClass;
import com.shure.encrypt.config.EncryptEntity;
import com.shure.encrypt.config.StorageEncryptProperties;
import com.shure.encrypt.encrypt.EncryptService;
import com.shure.encrypt.utils.EncryptDecryptConfigUtils;
import com.shure.encrypt.utils.EncryptDecryptUtils;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description TODO
 * @Author shure
 * @Date 2023/9/14 10:32
 * @Version 1.0
 **/
@AllArgsConstructor
public class ParameterInterceptor implements InnerInterceptor {

    private final StorageEncryptProperties storageEncryptProperties;
    private final EncryptService encryptService;

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        if (Objects.isNull(parameter)) {
            return;
        }

        if (!storageEncryptProperties.isEnable()) {
            return;
        }

        Class<?> cc = ms.getResultMaps().get(0).getType();
        List<String> parameterStr = buildFieldNames(cc);

        // 通过执行的方法取mapper类，在获取泛型实体
        if (parameterStr == null || parameterStr.isEmpty()) {
            // 获取到要执行的方法
            String id = ms.getId();
            try {
                Class clazz = Class.forName(id.substring(0, id.lastIndexOf(".")));
                Type[] types = clazz.getGenericInterfaces();
                if (types.length > 0) {
                    Type genericSuperclass = clazz.getGenericInterfaces()[0];
                    ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
                    cc = (Class) parameterizedType.getActualTypeArguments()[0];
                    parameterStr = buildFieldNames(cc);
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        // 再次判断是否需要加密
        if (parameterStr == null || parameterStr.isEmpty()) {
            return;
        }

        // 使用mybatis-plus提供的方法进行查询需要单独处理
        if (parameter instanceof MapperMethod.ParamMap) {
            Map param = (Map) parameter;
            int size = param.keySet().size() / 2;
            for (int i = 1; i <= size; i++) {
                String key = "param" + i;
                Object o = param.get(key);
                // QueryWrapper作为查询条件
                if (o instanceof AbstractWrapper) {
                    AbstractWrapper q = (AbstractWrapper) o;
                    if (Objects.isNull(q.getExpression()) || q.getExpression().getNormal().size() <= 0) {
                        return;
                    }

                    String sqlSegment = q.getExpression().getSqlSegment().toUpperCase();
                    parameterStr.forEach(s -> {
                        // 判断sql是否包含加密字段
                        if (sqlSegment.contains(s)) {
                            // 获取需要加密字段的值
                            String pKey = sqlSegment.split(s)[1].split("PARAMNAMEVALUEPAIRS.")[1].split("}")[0];
                            String pVal = q.getParamNameValuePairs().get(pKey).toString();
                            if (StringUtils.isNotBlank(pVal)) {
                                boolean start = false, end = false;
                                if (pVal.startsWith("%")) {
                                    pVal = pVal.substring(1);
                                    start = true;
                                }
                                if (pVal.endsWith("%")) {
                                    pVal = pVal.substring(0, pVal.length() - 1);
                                    end = true;
                                }
                                // 加密
                                pVal = encryptService.encrypt(pVal);
                                if (start)
                                    pVal = "%" + pVal;
                                if (end)
                                    pVal = pVal + "%";
                            }
                            // 覆盖为加密后内容
                            q.getParamNameValuePairs().put(pKey, pVal);
                        }
                    });

                    // Map作为查询条件
                } else if (o instanceof Map) {
                    Map map = (Map) o;
                    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
                    for (int j = 0; j < parameterMappings.size(); j++) {
                        ParameterMapping parameterMapping = parameterMappings.get(j);
                        if (parameterMapping.getMode() != ParameterMode.OUT) {
                            String propertyName = parameterMapping.getProperty();
                            String mapKey = propertyName.replaceFirst("__frch_v_", "__frch_k_");
                            if (map.containsKey(propertyName) && needEncrypt(parameterStr, propertyName)) {
                                map.put(propertyName, encryptService.encrypt(map.get(propertyName).toString()));
                            } else if (boundSql.hasAdditionalParameter(propertyName)
                                    && boundSql.hasAdditionalParameter(mapKey)
                                    && needEncrypt(parameterStr, boundSql.getAdditionalParameter(mapKey).toString())) {
                                boundSql.setAdditionalParameter(propertyName, encryptService.encrypt(boundSql.getAdditionalParameter(propertyName).toString()));
                            }
                        }
                    }

                } else if (Objects.nonNull(o)) {
                    // 实体类作为查询条件
                    BeanMap beanMap = BeanMap.create(o);
                    List<String> finalParameterStr = parameterStr;
                    beanMap.forEach((k, v) -> {
                        //判断是否需要加密，进行加密
                        if (Objects.nonNull(k) && Objects.nonNull(v)) {
                            if (needEncrypt(finalParameterStr, k.toString())) {
                                beanMap.put(k, encryptService.encrypt(v.toString()));
                            }
                        }
                    });
                }
            }
        }
        //InnerInterceptor.super.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
    }


    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {
        if (!storageEncryptProperties.isEnable()) {
            return;
        }

        if (Objects.isNull(parameter)) {
            return;
        }

        // 更新操作
        Object parameterObject = parameter;
        if (parameterObject instanceof MapperMethod.ParamMap) {
            MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameterObject;
            //避免相同的key重复执行，使用set对象记录堆栈地址
            HashSet<Integer> stackSet = new HashSet<>();
            for (Object key : paramMap.keySet()) {
                //需要使用hashcode判断，equals()方法可能被重写。
                if (stackSet.add(System.identityHashCode(paramMap.get(key)))) {
                    parameterObject = paramMap.get(key);
                    if (Objects.isNull(parameterObject)) {
                        return;
                    }
                }
            }
        }

        // 新增操作，注解方式
        Class<?> parameterObjectClass = parameterObject.getClass();
        EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptClass.class);
        if (Objects.nonNull(encryptDecryptClass)) {
            try {
                EncryptDecryptUtils.encrypt(EncryptDecryptUtils.getFields(parameterObjectClass), parameterObject, encryptService);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }

        // 配置文件方式
        List<EncryptEntity> entities = storageEncryptProperties.getEntities();
        if (!entities.isEmpty()) {
            try {
                EncryptDecryptUtils.encrypt(EncryptDecryptConfigUtils.getFields(parameterObjectClass, entities), parameterObject, encryptService);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private List<String> buildFieldNames(Class<?> clazz) {
        EncryptDecryptClass[] classes = clazz.getAnnotationsByType(EncryptDecryptClass.class);
        Field[] fields = new Field[0];
        if (classes.length == 0) {
            fields = EncryptDecryptConfigUtils.getFields(clazz, storageEncryptProperties.getEntities());
        } else {
            fields = EncryptDecryptUtils.getFields(clazz);
        }
        List<String> fieldNames = Arrays.stream(fields).map(str -> {
            String name = str.getName();
            name = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name);
            return name.toUpperCase();
        }).collect(Collectors.toList());
        return fieldNames;
    }


    private boolean needEncrypt(List<String> list, String key) {
        if (StringUtils.isNotBlank(key)) {
            key = key.toUpperCase();
        }
        return list.contains(key);
    }
}
